/*
 * Copyright (c) 2007-2011 by The Broad Institute of MIT and Harvard.All Rights Reserved.
 *
 * This software is licensed under the terms of the GNU Lesser General Public License (LGPL),
 * Version 2.1 which is available at http://www.opensource.org/licenses/lgpl-2.1.php.
 *
 * THE SOFTWARE IS PROVIDED "AS IS." THE BROAD AND MIT MAKE NO REPRESENTATIONS OR
 * WARRANTIES OF ANY KIND CONCERNING THE SOFTWARE, EXPRESS OR IMPLIED, INCLUDING,
 * WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER
 * OR NOT DISCOVERABLE.  IN NO EVENT SHALL THE BROAD OR MIT, OR THEIR RESPECTIVE
 * TRUSTEES, DIRECTORS, OFFICERS, EMPLOYEES, AND AFFILIATES BE LIABLE FOR ANY DAMAGES
 * OF ANY KIND, INCLUDING, WITHOUT LIMITATION, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
 * ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER
 * THE BROAD OR MIT SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT
 * SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING.
 */

package org.broad.tribble.source;

import org.apache.log4j.Logger;
import org.broad.tribble.Feature;
import org.broad.tribble.FeatureCodec;
import org.broad.tribble.TribbleException;
import org.broad.tribble.iterators.CloseableTribbleIterator;
import org.broad.tribble.readers.AsciiLineReader;
import org.broad.tribble.readers.LineReader;
import org.broad.tribble.util.ParsingUtils;

import java.io.IOException;
import java.util.Iterator;

/**
 * Basic iterator over features, intended to be subclassed
 * when necessary.
 * User: jacob
 * Date: 2012/01/26
 */
@SuppressWarnings("unchecked")
public class FeatureIterator<T extends Feature> implements CloseableTribbleIterator<T> {

    private static Logger log = Logger.getLogger(FeatureIterator.class);

    protected T currentRecord;

    protected LineReader reader;
    protected FeatureCodec codec;
    protected String inputFile;
    protected Object header;
    protected int line_number = 0;


    public FeatureIterator(BasicFeatureSource<T> basicFeatureSource) throws IOException {
        this(basicFeatureSource.path, basicFeatureSource.codec,
                basicFeatureSource.querySource.iterate());
    }


    public FeatureIterator(String inputFile, FeatureCodec codec, LineReader reader) throws IOException {
        this.inputFile = inputFile;
        this.codec = codec;
        this.reader = reader;
        this.init();
    }

    /**
     * I'm not sure why I need this, but it's necessary
     * for the code to compile
     */
    protected FeatureIterator() {
    }

    protected void init() throws IOException {
        this.header = readHeader(inputFile);
        this.advanceToFirstRecord();
    }

    /**
     * read the header from the file from an INDEPENDENT stream
     *
     * @return a Object, representing the file header, if available
     * @throws IOException throws an IOException if we can't open the file
     */
    protected Object readHeader(String path) throws IOException {
        Object header = null;

        //Open a new copy, because we can't rewind reader in general
        AsciiLineReader reader2 = null;
        try {
            reader2 = ParsingUtils.openAsciiReader(path);
            header = codec.readHeader(reader2);
        } catch (Exception e) {
            throw new TribbleException.MalformedFeatureFile("Unable to parse header with error: " + e.getMessage(), path, e);
        } finally {
            if (reader2 != null) {
                reader2.close();
            }
        }
        return header;
    }

    /**
     * read the next record, storing it in the currentRecord variable
     *
     * @throws IOException
     */
    protected void readNextRecord() throws IOException {
        currentRecord = null;
        String nextLine;
        while (currentRecord == null && reader != null && (nextLine = reader.readLine()) != null) {
            Feature f = null;
            this.line_number++;
            try {
                f = codec.decode(nextLine);
            } catch (TribbleException e) {
                e.setSource(inputFile);
                throw e;
            } catch (NumberFormatException e) {
                String error = "Error at or around line number " + this.line_number + " \n";
                error += "Line: " + nextLine + " \n";
                 throw new TribbleException.MalformedFeatureFile(error, this.inputFile, e);
            }
            if (f == null) {
                continue;
            }
            currentRecord = (T) f;
        }
    }

    /**
     * Called creating iterator, after reading header. Default implementation
     * is just readNextRecord
     *
     * @throws IOException
     */
    protected void advanceToFirstRecord() throws IOException {
        this.readNextRecord();
    }

    public boolean hasNext() {
        return currentRecord != null;
    }

    public T next() {
        T ret = currentRecord;
        try {
            readNextRecord();
        } catch (IOException e) {
            throw new RuntimeException("Unable to read the next record, the last record was at " + ret.getChr() + ":" + ret.getStart() + "-" + ret.getEnd(), e);
        }
        return ret;

    }

    public void remove() {
        throw new UnsupportedOperationException("Remove is not supported in Iterators");
    }


    public void close() {
        // we don't have anything open, so don't sweat it
    }

    public Iterator<T> iterator() {
        return this;
    }

}

